package com.kili.cloud.common.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author tom.liu
 * @description
 * @date 2019/6/21 16:45
 */
@Slf4j
@Service
public class RedisLock {
    private static final Long RELEASE_SUCCESS = 1L;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 该加锁方法仅针对单实例 Redis 可实现分布式加锁
     * 对于 Redis 集群则无法使用
     * 支持重复，线程安全
     * @param lockKey   加锁键
     * @param requestId  加锁客户端唯一标识
     * @param expireTime 过期时间
     * @return
     */
    public boolean distributedLock(String lockKey, String requestId, int expireTime) {
        boolean success = stringRedisTemplate.execute((RedisCallback<Boolean>) connection ->
                connection.set(lockKey.getBytes(), requestId.getBytes(), Expiration.from(expireTime, TimeUnit.SECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT));
        return success;
    }

    /**
     * 功能描述: 基于spring-boot-starter-data-redis的分布式锁
     * @param lockKey 加锁键
     * @param requestId 加锁客户端唯一标识
     * @return
     * @author tom.liu
     * @date 2019/6/21 16:53
     */
    public boolean releaseDistributedLock(String lockKey, String requestId) {
        String scriptStr = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> script = new DefaultRedisScript(scriptStr, Long.class);
        List<String> keys = new ArrayList<>();
        keys.add(lockKey);
        Long result = stringRedisTemplate.execute(script, new StringRedisSerializer(), new RedisSerializer<Long>() {
            private final Charset charset = Charset.forName("UTF8");

            @Override
            public byte[] serialize(Long aLong) throws SerializationException {
                return (aLong == null ? null : (aLong.toString()).getBytes(charset));
            }

            @Override
            public Long deserialize(byte[] bytes) throws SerializationException {
                return (bytes == null ? null : Long.parseLong(new String(bytes, charset)));
            }
        }, keys, requestId);
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}
